/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License.You may obtain a copy of the License at
	http://www.mozilla.org/MPL/MPL-1.1.html
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import com.bourre.commands.Delegate;
import com.bourre.core.AccessorFactory;
import com.bourre.core.HashCodeFactory;
import com.bourre.core.IAccessor;
import com.bourre.data.collections.Map;
import com.bourre.data.iterator.Iterator;
import com.bourre.data.libs.BasicXMLDeserializer;
import com.bourre.data.libs.ConfigLoader;
import com.bourre.data.libs.LibStack;
import com.bourre.data.libs.XMLToObject;
import com.bourre.data.libs.XMLToObjectDeserializer;
import com.bourre.data.libs.XMLToObjectEvent;
import com.bourre.events.IEvent;
import com.bourre.ioc.log.PixiocDebug;
import com.bourre.log.PixlibStringifier;

import fever.app.local.Culture;
import fever.app.local.Local;
import fever.app.local.Localisation;
import fever.app.local.LocalisationEvent;
import fever.app.local.LocalisationListener;
import fever.events.EventPriority;
import fever.Fever;
import fever.ioc.plugin.FeverPlugin;
import fever.utils.StringUtil;

/**
 * Plugin localisation engine.
 * 
 * @author Romain Ecarnot
 */
class fever.ioc.plugin.PluginLocalisation implements LocalisationListener
{
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private static var _M : Map = new Map();
	
	private var _owner : FeverPlugin;	private var _logger : PixiocDebug;
	private var _clientLang : String;
	private var _connected : Boolean;
	private var _currentCulture : Culture;
	private var _loaded : Boolean;	private var _bInitialized : Boolean;
	private var _loaderStack : LibStack;
	private var _localMap : Map;	private var _cultureMap : Map;
	private var _repository : Object;
	private var _accessorMap : Map;
	private var _accessor : IAccessor;
	private var _handler : Delegate;
	
	
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Returns {@link PluginLocalisation} instance registred with passed-in 
	 * {@code owner} plugin.
	 */
	public static function getInstance( owner : FeverPlugin ) 
		: PluginLocalisation
	{
		if ( !( _M.containsKey( owner ) ) ) 
			_M.put( owner, new PluginLocalisation( owner ) );
			
		return _M.get( owner );
	}
	
	/**
	 * Returns plugin owner.
	 */
	public function getOwner() : FeverPlugin
	{
		return _owner;
	}
	
	/**
	 * Retreives passed-in {@code id} local resource in current 
	 * language map.
	 * 
	 * @param id Translation node id to retreive
	 * 
	 * @return resource or {@code null} if {@code id} is not defined 
	 * in map.
	 */
	public function getResource( id : String )
	{
		return _getResource( id );
	}
	
	/**
	 * Retreives passed-in {@code id} local resource in current 
	 * language map.
	 * 
	 * <p>Return is <strong>String</strong> typed.
	 * 
	 * @param id Translation node id to retreive
	 * 
	 * @return resource string or {@code null} if {@code id} is not defined 
	 * in map.
	 */
	public function getResourceAsString( id : String ) : String
	{
		var o : String = String( _getResource( id ) );
		o = StringUtil.replace( o, '\\n', '\n' );
		
		return o;
	}
	
	/**
	 * Retreives passed-in {@code id} local resource in current 
	 * language map.
	 * 
	 * <p>Return is <strong>Boolean</strong> typed.
	 * 
	 * @param id Translation node id to retreive
	 * 
	 * @return resource string or {@code null} if {@code id} is not defined 
	 * in map.
	 */
	public function getResourceAsBoolean( id : String ) : Boolean
	{
		return Boolean( _getResource( id ) );
	}
	
	/**
	 * Retreives passed-in {@code id} local resource in current 
	 * language map.
	 * 
	 * <p>Return is <strong>Number</strong> typed.
	 * 
	 * @param id Translation node id to retreive
	 * 
	 * @return resource string or {@code null} if {@code id} is not defined 
	 * in map.
	 */
	public function getResourceAsNumber( id : String ) : Number
	{
		return Number( _getResource( id ) );
	}
	
	/**
	 * Returns {@code true} if passed-in {@code id} exists in Localisation 
	 * data.
	 * 
	 * <p>Pass full node namespace to search in global Localisation object.
	 * 
	 * <p>Example
	 * 	var b : Boolean = hasResource( 'fvaswing.alert.okLabel ');
	 * 	
	 * 	@param id Translation node id
	 */
	public function hasResource( id : String ) : Boolean
	{
		return ( _getResource( id ) != undefined );
	}
	
	/**
	 * Registers an {@code instance} property by his passed-in 
	 * {@code setter} method.
	 * 
	 * @param instance * Target instance
	 * @param setter {@code String} or {code Function} property access
	 * @param id Language key to watch
	 * @param substitution ( optional ) if apply, use a 
	 * {@code StringUtil.substitute()} to return translation.
	 */
	public function connectInstance( 
		instance : Object, 
		setter, 
		id : String, 
		substitution : Object ) : Void
	{
		var accessor : IAccessor = AccessorFactory.getInstance( 
			instance, 
			setter, 
			null 
		);
		
		_connect( accessor, id, substitution );
	}
	
	/**
	 * Registers an {@code IAccessor} instance
	 * 
	 * @param accessor IAccessor instance
	 * @param id Language key to watch
	 * @param substitution ( optional ) if apply, use a 
	 * {@code StringUtil.substitute()} to return translation.
	 */
	public function connectAccessor( 
		accessor : IAccessor, 
		id : String, 
		substitution : Object ) : Void
	{
		_connect( accessor, id, substitution );	
	}
	
	/**
	 * Unregisters instance property.
	 * 
	 * @param instance * Target instance
	 */
	public function disconnectInstance( instance : Object ) : Void
	{
		_disconnect( instance );
	}
	
	/**
	 * Unregisters accessor.
	 * 
	 * @param instance IAccessor instance
	 */
	public function disconnectAccessor( accessor : IAccessor ) : Void
	{
		_disconnect( accessor.getTarget() );
	}
	
	/**
	 * Triggered when Localisation language change.
	 */
	public function onLocalisationUpdate( event : LocalisationEvent ) : Void
	{
		_currentCulture = _cultureMap.get( Localisation.lang );
		
		_update();
	}
	
	/**
	 * Don't use this method, it is just call by the internal Fever engine 
	 * for the moment.
	 */
	public function init( langList : Object, handler : Delegate ) : Void
	{
		if( !_bInitialized )
		{ 
			_bInitialized = true;
			_handler = handler;
			
			_init( langList );
		}
	}
	
	/**
	 * Returns the string representation of this instance.
	 */
	public function toString() : String 
	{
		return PixlibStringifier.stringify( this ) + 
			( _owner? ', owner : '+ _owner : 'No owner.' );
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * 
	 * @param owner IOC Plugin owner
	 */
	private function PluginLocalisation( owner : FeverPlugin )
	{
		_owner = owner;
		_logger = PixiocDebug.getInstance( getOwner() );
		
		_clientLang = Fever.client.language;
		
		_localMap = new Map();		_cultureMap = new Map();
		_repository = new Object();
		_accessorMap = new Map();
		
		_connected = false;
		_loaded = false;
	}
	
	private function _init( langList : Object ) : Void
	{
		if( langList instanceof Array )
		{
			var l : Number = langList.length;
			
			for( var i : Number = 0; i < l; i++ )
			{
				var lang : Object = langList[ i ];
				
				_push( new Culture( lang.id, lang.label ) );	
			}
		}
		else _push( new Culture( langList.id, langList.label ) );
		
		_load();
	}
	
	private function _push( culture : Culture ) : Void
	{
		var id : String = culture.id;
		var link : String = getOwner().getConfig().getLocalePath() 
							+ id + '.locale';
		
		if( _localMap.containsKey( id ) )
			_logger.warn( 
				getOwner().getName() + ' Language id : ' + 
				id + ' is already registred' 
			);
		else 
		{
			_cultureMap.put( id, culture );
			_localMap.put( id, link );
		}
	}
	
	private function _load( id : String ) : Void
	{
		if( _localMap.isEmpty() || !Localisation.isSupported() )
		{
			_logger.warn( 
				getOwner().getName() + ' Plugin Localisation map is empty' 
			);
			
			 _onLoadComplete( _clientLang );
			 return;
		}
		
		Localisation.addLocalisationListener( 
			this, 
			EventPriority.LOCALISATION_MANAGMENT 
		);
		
		_loaderStack = new LibStack();
		
		var iterator : Iterator = _localMap.getKeysIterator();
		while( iterator.hasNext() )
		{
			var key : String = iterator.next();
			var link : String = _localMap.get( key );
			var local : Local = new Local( key );
			var loader : ConfigLoader = new ConfigLoader( 
				local, 
				new BasicXMLDeserializer() 
			);
			
			loader.getContent().addEventListener( 
				XMLToObject.onErrorEVENT, 
				this, 
				_onError, 
				key 
			);
			
			_loaderStack.enqueue( loader, key, link );
			
			_repository[ key ] = local;
		}	
		
		XMLToObjectDeserializer.PUSHINARRAY_IDENTICAL_NODE_NAMES = true;
		
		_loaderStack.addEventListener( 
			LibStack.onLoadCompleteEVENT, this, _onLoadConfig, id );
		
		if( !_loaderStack.isEmpty() ) _loaderStack.load();
		else _onLoadComplete( null );
	}
	
	private function _connect( 
		accessor : IAccessor, 
		id : String, 
		substitution : Object ) : Void
	{
		if( _accessorMap.isEmpty() ) _connected = true;
		
		var key : Number = HashCodeFactory.getKey( accessor.getTarget() );
		if( !_accessorMap.containsKey( key ) )
		{
			_accessorMap.put( 
				key, 
				{ access : accessor, id : id, substitution : substitution } 
			);
			
			if( _loaded  )
			{
				if( substitution )
				{
					if( substitution instanceof Array )
					{
						accessor.setValue( 
							StringUtil.substituteByArray( 
								_getResource( id ),
								Array( substitution ) 
							) 
						);
					}
					else
					{
						accessor.setValue( 
							StringUtil.substitute( 
								_getResource( id ), 
								substitution 
							) 
						);
					}
				}
				else accessor.setValue( _getResource( id ) );
			}
		}
	}
	
	private function _disconnect ( instance : Object ) : Void
	{
		if( !instance ) return;
		
		_accessorMap.remove( HashCodeFactory.getKey(instance) );
		
		if( _accessorMap.isEmpty()) _connected = false;
	}
	
	private function _update() : Void
	{
		if( _connected )
		{
			var iterator : Iterator = _accessorMap.getValuesIterator();
			
			while( iterator.hasNext() )
			{
				var o : Object = iterator.next();
				var f : IAccessor = o.access;
				var id : String = o.id;
				var sub : Object = o.substitution;
				
				if( sub )
				{
					if( sub instanceof Array )
					{
						f.setValue( StringUtil.substituteByArray( 
							_getResource( id ), 
							Array( sub ) ) 
						);
					}
					else
					{
						f.setValue( 
							StringUtil.substitute( _getResource( id ), sub ) 
						);
					}
				}
				else f.setValue( _getResource( id ) );
			}
		}
	}
	
	private function _getConfigDeserializer( o : ConfigLoader ) 
		: XMLToObjectDeserializer
	{
		return XMLToObjectDeserializer( 
			XMLToObject( o.getContent() ).getDeserializer() 
		);
	}
	
	private function _onLoadConfig(event : IEvent, id : String) : Void
	{
		_onLoadComplete( id );
	}
	
	private function _onLoadComplete( id : String ) : Void
	{
		_loaded = true;
		
		onLocalisationUpdate();
		
		_callHander();
	}
	
	private function _onError( event : XMLToObjectEvent, id  : String ) : Void
	{
		_logger.fatal( 
			getOwner().getName() + ' Localisation error ' + 
			event.getLib().getURL()
		);
		
		_loaded = false;
		
		XMLToObject( event.getTarget() ).release();
		XMLToObject( event.getTarget() ).removeEventListener( 
			XMLToObject.onErrorEVENT, 
			this 
		);
		
		_loaderStack.release();
		_loaderStack.removeEventListener( LibStack.onLoadCompleteEVENT, this );
		
		_cultureMap.remove( id );
		_callHander();
	}
	
	private function _getResource( id : String )
	{
		var oList : Array = id.split( '.' );
		var l : Number = oList.length;
		var o = _repository[ Localisation.lang ];
		
		for( var i : Number = 0; i < l; i++ ) o = o[ oList[i] ];
		if( !o ) _logger.error( 
			getOwner() + ' ' + id + ' is undefined in ' + 
			Localisation.lang + ' culture' 
		);
		
		return o;
	}
	
	private function _callHander( ) : Void
	{
		_handler.setArguments( [_loaded] );
		_handler.execute();	
	}
}